﻿using System.Windows;
using System.Windows.Controls;

namespace YourLastOptionsDialog.Controls
{
    /// <summary>
    /// A validating text box that shows a validation error message if a validator has been configured and failed validation.
    /// </summary>
    public sealed class ValidatingTextBox : TextBox
    {
        private TextBlock _validationMessageTextBlock;

        /// <summary>
        /// Identifies the <see cref="ValidatorProperty"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ValidatorProperty = DependencyProperty.Register(
            "Validator", 
            typeof(IValidator), 
            typeof(ValidatingTextBox), 
            new PropertyMetadata(default(IValidator), null));

        /// <summary>
        /// Gets or sets the validator used for validation.
        /// </summary>
        public IValidator Validator
        {
            get
            {
                return (IValidator)GetValue(ValidatorProperty);
            }

            set
            {
                SetValue(ValidatorProperty, value);
            }
        }

        /// <summary>
        /// Identifies the <see cref="ValidationErrorMessage"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ValidationErrorMessageProperty = DependencyProperty.Register(
            "ValidationErrorMessage", 
            typeof(string), 
            typeof(ValidatingTextBox), 
            new PropertyMetadata("Invalid value.", ValidationErrorMessage_Changed));

        private static void ValidationErrorMessage_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = d as ValidatingTextBox;
            if (obj != null)
            {
                var textBlock = obj._validationMessageTextBlock;
                if (textBlock != null)
                {
                    textBlock.Text = (string)e.NewValue;
                }
            }
        }

        /// <summary>
        /// Gets or sets the validation error message that is displayed when the validation fails.
        /// </summary>
        public string ValidationErrorMessage
        {
            get
            {
                return (string)GetValue(ValidationErrorMessageProperty);
            }

            set
            {
                SetValue(ValidationErrorMessageProperty, value);
            }
        }

        /// <summary>
        /// Identifies the <see cref="ValidationErrorMessageVisibility"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ValidationErrorMessageVisibilityProperty = DependencyProperty.Register(
            "ValidationErrorMessageVisibility", 
            typeof(Visibility), 
            typeof(ValidatingTextBox), 
            new PropertyMetadata(Visibility.Visible, null));

        /// <summary>
        /// Gets or sets the validation error message visibility that determines whether the text box should show 
        /// its own validation error message. If a user of the text box wants to display their own validation error,
        /// this can be set to <see cref="Visibility.Collapsed"/> to suppress the display of the validation message.
        /// </summary>
        public Visibility ValidationErrorMessageVisibility
        {
            get
            {
                return (Visibility)GetValue(ValidationErrorMessageVisibilityProperty);
            }

            set
            {
                SetValue(ValidationErrorMessageVisibilityProperty, value);
            }
        }

        /// <summary>
        /// Identifies the <see cref="ActualValidationErrorMessageVisibility"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ActualValidationErrorMessageVisibilityProperty = DependencyProperty.Register(
            "ActualValidationErrorMessageVisibility", 
            typeof(Visibility), 
            typeof(ValidatingTextBox), 
            new PropertyMetadata(Visibility.Collapsed, null));

        /// <summary>
        /// Gets or sets the actual validation error message visibility. The value of this property
        /// depends on whether the current state of the content is valid or not, and whether the
        /// text box is allowed to display its own validation error message or not.
        /// </summary>
        public Visibility ActualValidationErrorMessageVisibility
        {
            get
            {
                return (Visibility)GetValue(ActualValidationErrorMessageVisibilityProperty);
            }

            set
            {
                SetValue(ActualValidationErrorMessageVisibilityProperty, value);
            }
        }

        /// <summary>
        /// Identifies the <see cref="IsValid"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsValidProperty = DependencyProperty.Register(
            "IsValid", 
            typeof(bool), 
            typeof(ValidatingTextBox), 
            new PropertyMetadata(true, IsValid_Changed));

        private static void IsValid_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = d as ValidatingTextBox;
            if (obj != null)
            {
                var newValue = (bool)e.NewValue;
                if (newValue)
                {
                    obj.ActualValidationErrorMessageVisibility = Visibility.Collapsed;
                    VisualStateManager.GoToState(obj, "Valid", true);
                }
                else
                {
                    obj.ActualValidationErrorMessageVisibility = obj.ValidationErrorMessageVisibility;
                    VisualStateManager.GoToState(obj, "Invalid", true);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the value of the text box is valid or not.
        /// </summary>
        /// <value>
        ///   <c>true</c> if the value is valid; otherwise, <c>false</c>.
        /// </value>
        public bool IsValid
        {
            get
            {
                return (bool)GetValue(IsValidProperty);
            }

            set
            {
                SetValue(IsValidProperty, value);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidatingTextBox"/> class.
        /// </summary>
        public ValidatingTextBox()
        {
            DefaultStyleKey = typeof(ValidatingTextBox);
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>. In simplest terms, this means the method is called just before a UI element displays in an application. For more information, see Remarks.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _validationMessageTextBlock = GetTemplateChild("ValidationMessageTextBlock") as TextBlock;
        }

        /// <summary>
        /// Called before <see cref="E:System.Windows.UIElement.LostFocus"/> event occurs.
        /// </summary>
        /// <param name="e">The data for the event.</param>
        protected override void OnLostFocus(RoutedEventArgs e)
        {
            var validationResult = Validator != null ? Validator.Validate(Text) : ValidationResult.Success;
            ValidationErrorMessage = validationResult.ValidationErrorMessage;
            IsValid = validationResult.IsValid;

            base.OnLostFocus(e);
        }
    }
}